﻿#include	"Engine.File.hpp"
#include	"Engine.String.hpp"

#include	<cstdio>
#include	<cstdlib>
#include	<cstring>
#include	<fstream>

namespace Engine { namespace File {
	
	/// 去掉空白字符
	static char * IniSkipWhiteSpace(char * pStr) {
		if (!pStr || !(*pStr)) return pStr;

		char * pDst = pStr;

		while (*pDst) {
			if (*pDst == ' ' || *pDst == '\t' || *pDst == '\r' || *pDst == '\n')
				++pDst;
			else
				break;
		}

		return pDst;
	}

	/// 解析Session
	static int IniParseSession(char ** pSrc, char * pStore) {
		char * pRead = IniSkipWhiteSpace(*pSrc);
		if (!*pRead) return IniFile::ErrLostSession;
		
		bool bNeedQuote = false;
		size_t nWritePos = 0;

		while (*pRead) {
			char nCur = *pRead;
			
			if (bNeedQuote) {
				if (nCur == '\"') {
					bNeedQuote = false;
					if (nWritePos == 0) {
						return IniFile::ErrSessionSyntax;
					} else {
						pRead = IniSkipWhiteSpace(pRead + 1);
						if (!*pRead)
							return IniFile::ErrBraceMismatch;

						if (*pRead != ']')
							return IniFile::ErrSessionSyntax;

						pRead = IniSkipWhiteSpace(pRead + 1);
						if (*pRead && *pRead != '#' && *pRead != ';')
							return IniFile::ErrSessionSyntax;

						return IniFile::Success;
					}
				} else {
					pStore[nWritePos] = nCur;
					++nWritePos;
				}
			} else if (nCur == '\"') {
				if (nWritePos != 0)
					return IniFile::ErrSessionSyntax;

				bNeedQuote = true;
			} else if (nCur == '#' || nCur == ';') {
				if (nWritePos != 0)
					return IniFile::ErrBraceMismatch;
				else
					return IniFile::ErrLostSession;
			} else if (nCur == ' ' || nCur == '\t' || nCur == '\r' || nCur == '\n') {
				pRead = IniSkipWhiteSpace(pRead + 1);
				if (!*pRead || *pRead == '#' || *pRead == ';')
					return IniFile::ErrBraceMismatch;

				if (*pRead != ']')
					return IniFile::ErrSessionSyntax;

				pRead = IniSkipWhiteSpace(pRead + 1);
				if (*pRead && *pRead != '#' && *pRead != ';')
					return IniFile::ErrSessionSyntax;

				return IniFile::Success;
			} else if (nCur == '[' || nCur == '=') {
				return IniFile::ErrSessionSyntax;
			} else if (nCur == ']') {
				if (nWritePos == 0)
					return IniFile::ErrLostSession;

				pRead = IniSkipWhiteSpace(pRead + 1);
				if (*pRead && *pRead != '#' && *pRead != ';')
					return IniFile::ErrSessionSyntax;

				return IniFile::Success;
			} else {
				pStore[nWritePos] = nCur;
				++nWritePos;
			}
			++pRead;
		}

		if (bNeedQuote)	return IniFile::ErrQuoteMismatch;
		if (nWritePos == 0) return IniFile::ErrLostSession;
		return IniFile::ErrBraceMismatch;
	}

	/// 解析Key
	static int IniParseKey(char ** pSrc, char * pStore) {
		char * pRead = IniSkipWhiteSpace(*pSrc);
		if (!*pRead) return IniFile::ErrLostKey;

		bool bNeedQuote = false;
		size_t nWritePos = 0;

		while (*pRead) {
			char nCur = *pRead;

			if (bNeedQuote) {
				if (nCur == '\"') {
					bNeedQuote = false;
					if (nWritePos == 0) {
						return IniFile::ErrKeySyntax;
					} else {
						pRead = IniSkipWhiteSpace(pRead + 1);
						if (!*pRead)
							return IniFile::ErrEqualMismatch;

						if (*pRead != '=')
							return IniFile::ErrKeySyntax;
						
						*pSrc = pRead + 1;
						return IniFile::Success;
					}
				} else {
					pStore[nWritePos] = nCur;
					++nWritePos;
				}
			} else if (nCur == '\"') {
				if (nWritePos != 0)
					return IniFile::ErrKeySyntax;

				bNeedQuote = true;
			} else if (nCur == '#' || nCur == ';' || nCur == '[' || nCur == ']') {
				return IniFile::ErrKeySyntax;
			} else if (nCur == ' ' || nCur == '\t' || nCur == '\r' || nCur == '\n') {
				pRead = IniSkipWhiteSpace(pRead + 1);
				if (!*pRead || *pRead == '#' || *pRead == ';')
					return IniFile::ErrEqualMismatch;

				if (*pRead != '=')
					return IniFile::ErrKeySyntax;

				*pSrc = pRead + 1;
				return IniFile::Success;
			} else if (nCur == '=') {
				if (nWritePos == 0)
					return IniFile::ErrLostKey;

				*pSrc = pRead + 1;
				return IniFile::Success;
			} else {
				pStore[nWritePos] = nCur;
				++nWritePos;
			}

			++pRead;
		}

		if (bNeedQuote)	return IniFile::ErrQuoteMismatch;
		if (nWritePos == 0) return IniFile::ErrLostKey;
		return IniFile::ErrEqualMismatch;
	}

	/// 解析Value
	static int IniParseValue(char ** pSrc, char * pStore) {
		char * pRead = IniSkipWhiteSpace(*pSrc);
		if (!*pRead) return IniFile::Success;

		bool bNeedQuote = false;
		size_t nWritePos = 0;

		while (*pRead) {
			char nCur = *pRead;

			if (bNeedQuote) {
				if (nCur == '\"') {
					bNeedQuote = false;
					if (nWritePos == 0) {
						return IniFile::Success;
					} else {
						pRead = IniSkipWhiteSpace(pRead + 1);
						if (!*pRead || *pRead == '#' || *pRead == ';')
							return IniFile::Success;
						else
							return IniFile::ErrValueSyntax;
					}
				} else {
					pStore[nWritePos] = nCur;
					++nWritePos;
				}
			} else if (nCur == '\"') {
				if (nWritePos != 0)
					return IniFile::ErrValueSyntax;

				bNeedQuote = true;
			} else if (nCur == '[' || nCur == ']' || nCur == '=') {
				return IniFile::ErrValueSyntax;
			} else if (nCur == '#' || nCur == ';') {
				return IniFile::Success;
			} else if (nCur == ' ' || nCur == '\t' || nCur == '\r' || nCur == '\n') {
				pRead = IniSkipWhiteSpace(pRead + 1);
				if (!*pRead || (*pRead != '#' && *pRead != ';'))
					return IniFile::ErrValueSyntax;

				return IniFile::Success;
			} else {
				pStore[nWritePos] = nCur;
				++nWritePos;
			}

			++pRead;
		}

		if (bNeedQuote)	return IniFile::ErrQuoteMismatch;
		return IniFile::Success;
	}
	
	int IniFile::Load(const char * szFile) {
		if (!szFile || ::strlen(szFile) <= 0) return ErrBadParam;

		FILE * pFile = NULL;

#if defined(_WIN32) && defined(_MSC_VER)
		if (fopen_s(&pFile, szFile, "r") != 0) {
#else
		if ((pFile = fopen(szFile, "r")) == NULL) {
#endif
			return ErrFileAccess;
		}

		char pLine[1024], pStore[1024];
		unsigned char pBomb[3] = { 0,0,0 };
		std::string szCurSession, szCurKey;

		int nCount = fread(pBomb, 1, 3, pFile);
		if (nCount < 3 || !(pBomb[0] == 0xEF && pBomb[1] == 0xBB && pBomb[2] == 0xBF)) {
			fseek(pFile, 0, SEEK_SET);
		}

		while (!feof(pFile)) {
			::memset(pLine, 0, 1024);
			::memset(pStore, 0, 1024);

			szCurKey.clear();			
			fgets(pLine, 1024, pFile);

			int nLen = strlen(pLine);
			if (nLen >= 1024) return ErrLineSize;
			for (int i = nLen - 1; i >= 0; --i) {
				char nChar = pLine[i];
				if (nChar == ' ' || nChar == '\t' || nChar == '\r' || nChar == '\n') pLine[i] = 0;
				else break;
			}
			
			char * pRead = IniSkipWhiteSpace(pLine);
			int nRet = IniFile::Success;

			if (*pRead == '\0' || *pRead == '#' || *pRead == ';') {
				continue;
			} else if (*pRead == '[') {
				++pRead;				
				if ((nRet = IniParseSession(&pRead, pStore)) != IniFile::Success)
					return nRet;
				szCurSession.assign(pStore);
			} else if (szCurSession.empty()) {
				return IniFile::ErrLostSession;
			} else {
				if ((nRet = IniParseKey(&pRead, pStore)) != IniFile::Success)
					return nRet;

				szCurKey.assign(pStore);
				::memset(pStore, 0, 1024);
				
				if ((nRet = IniParseValue(&pRead, pStore)) != IniFile::Success)
					return nRet;

				if (pStore[0]) {
					if (szCurSession.empty())
						return ErrLostSession;

					if (szCurKey.empty())
						return ErrLostKey;

					auto itor = _iMapContent.find(szCurSession);
					if (itor != _iMapContent.end()) {
						itor->second[szCurKey] = pStore;
					} else {
						HashMapValue iKeyValue;
						iKeyValue[szCurKey] = pStore;
						_iMapContent[szCurSession] = iKeyValue;
					}
				}
			}
		}

		fclose(pFile);
		return IniFile::Success;		
	}

	int IniFile::Save(const char * szFile) {
		if (!szFile || ::strlen(szFile) <= 0) return ErrBadParam;

		std::ofstream iFile(szFile, std::ifstream::out);
		if (!iFile.is_open()) return ErrFileAccess;

		for (auto itorMain = _iMapContent.begin(); itorMain != _iMapContent.end(); ++itorMain) {
			std::string szSession = itorMain->first;

			if (szSession.find_first_of("[]= \t\r\n") != std::string::npos) {
				iFile << "[\"" << itorMain->first << "\"]" << std::endl;
			} else {
				iFile << "[" << itorMain->first << "]" << std::endl;
			}

			
			for (auto itorSub = itorMain->second.begin(); itorSub != itorMain->second.end(); ++itorSub) {
				std::string szKey = itorSub->first;
				std::string szValue = itorSub->second;

				if (szKey.find_first_of("[]= \t\r\n") != std::string::npos) {
					iFile << "\"" << szKey << "\"=";
				} else {
					iFile << szKey << "=";
				}

				if (szValue.find_first_of("[]= \t\r\n") != std::string::npos) {
					iFile << "\"" << szValue << "\"" << std::endl;
				} else {
					iFile << szValue << std::endl;
				}
			}

			iFile << "\n" << std::endl;
		}

		return true;
	}

	int IniFile::GetInt(const char * szSession, const char * szKey, int nDefault) {
		auto itorMain = _iMapContent.find(szSession);
		if (itorMain != _iMapContent.end()) {
			auto itorSub = itorMain->second.find(szKey);
			if (itorSub != itorMain->second.end())
				return ::atoi(itorSub->second.c_str());
			return nDefault;
		}

		return nDefault;
	}

	double IniFile::GetDouble(const char * szSession, const char * szKey, double dDefault) {
		auto itorMain = _iMapContent.find(szSession);
		if (itorMain != _iMapContent.end()) {
			auto itorSub = itorMain->second.find(szKey);
			if (itorSub != itorMain->second.end())
				return ::atof(itorSub->second.c_str());
			return dDefault;
		}

		return dDefault;
	}

	bool IniFile::GetBool(const char * szSession, const char * szKey, bool bDefault) {
		auto itorMain = _iMapContent.find(szSession);
		if (itorMain != _iMapContent.end()) {
			auto itorSub = itorMain->second.find(szKey);
			if (itorSub != itorMain->second.end()) {
				std::string szTemp = itorSub->second;
				if (szTemp.compare("T") == 0 ||
					szTemp.compare("t") == 0 ||
					szTemp.compare("TRUE") == 0 ||
					szTemp.compare("true") == 0)
					return true;

				if (szTemp.compare("F") == 0 ||
					szTemp.compare("f") == 0 ||
					szTemp.compare("FALSE") == 0 ||
					szTemp.compare("false") == 0)
					return false;

				int n = ::atoi(itorSub->second.c_str());
				return n != 0;
			}

			return bDefault;
		}

		return bDefault;
	}

	std::string IniFile::GetString(const char * szSession, const char * szKey, const std::string & szDefault) {
		auto itorMain = _iMapContent.find(szSession);
		if (itorMain != _iMapContent.end()) {
			auto itorSub = itorMain->second.find(szKey);
			if (itorSub != itorMain->second.end())
				return itorSub->second;
			return szDefault;
		}

		return szDefault;
	}

	void IniFile::SetInt(const char * szSession, const char * szKey, int nValue) {
		std::string szValue = String::Format("%d", nValue);
		SetString(szSession, szKey, szValue);
	}

	void IniFile::SetDouble(const char * szSession, const char * szKey, double dValue) {
		std::string szValue = String::Format("%lf", dValue);
		SetString(szSession, szKey, szValue);
	}

	void IniFile::SetBool(const char * szSession, const char * szKey, bool bValue) {
		std::string szValue = bValue ? "TRUE" : "FALSE";
		SetString(szSession, szKey, szValue);
	}

	void IniFile::SetString(const char * szSession, const char * szKey, const std::string & szValue) {
		auto itorMain = _iMapContent.find(szSession);
		if (itorMain != _iMapContent.end()) {
			itorMain->second[szKey] = szValue;
		} else {
			HashMapValue iKeyValue;
			iKeyValue[szKey] = szValue;
			_iMapContent[szSession] = iKeyValue;
		}
	}
}}
